//////////////////////////////////////////////////////////////////////////

#ifndef _BOOSTSERVERCONNECTIONIMPL_H_
#include "boostserverconnectionimpl.h"
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef _INNERPACKET_H_
#include "innerpacket.h"
#endif

#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>

//////////////////////////////////////////////////////////////////////////

// Alloc memory for static fields


// IO_SERVICE ptr
io_service* BoostServerConnectionImpl::m_IOService = NULL;

// TCP acceptor ptr
tcp::acceptor* BoostServerConnectionImpl::m_Acceptor = NULL;

// UDP socket ptr
udp::socket* BoostServerConnectionImpl::m_UDPSocket = NULL;

// Flags
bool BoostServerConnectionImpl::m_Started = false;
bool BoostServerConnectionImpl::m_UDPAcceptStarted = false;

// Thread
boost::thread BoostServerConnectionImpl::m_Thread;

// UDP endpoint
udp::endpoint BoostServerConnectionImpl::m_UDPRecvEndpoint;

// Connections by endpoints
BoostServerConnectionImpl::ConnectionsByEndpoints
BoostServerConnectionImpl::m_ConnectionsByEndpoints;

// Endpoints by connections
BoostServerConnectionImpl::EndpointsByConnections
BoostServerConnectionImpl::m_EndpointsByConnections;

// Connections by address
BoostServerConnectionImpl::ConnectionsByAddress
BoostServerConnectionImpl::m_ConnectionsByAddress;



//////////////////////////////////////////////////////////////////////////


BoostServerConnectionImpl::BoostServerConnectionImpl()
: m_TCPSocket( *GetIOService() )
{
}


//////////////////////////////////////////////////////////////////////////


BoostServerConnectionImpl::~BoostServerConnectionImpl()
{
}


//////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::SendTCPInner( const Packet& _data )
{
    // Send data through TCP
    m_TCPSocket.async_send( boost::asio::buffer( _data.Data(), _data.Size() ),
        boost::bind( &BoostServerConnectionImpl::HandleSendTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


//////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::SendUDPInner( const Packet& _data )
{
    // Send data through UDP
    GetUDPSocket()->async_send_to( boost::asio::buffer( _data.Data(), _data.Size() ),
        GetUDPEndpoint(this),
        boost::bind( &BoostServerConnectionImpl::HandleSendUDP,
        this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


//////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::SendTCP( const Packet& _data )
{
    // Create inner packet from user packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // Send inner packet
    SendTCPInner(innerPacket);
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::SendUDP( const Packet& _data )
{
    // Create inner packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // And send it
    SendUDPInner(innerPacket);
}



////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::StartReceiveTCP()
{
    // Recv data through TCP
    m_TCPSocket.async_receive( boost::asio::buffer(m_RecvBufferTCP, MAX_BUFFER_SIZE),
        boost::bind( &BoostServerConnectionImpl::HandleRecvTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::StartReceiveUDP()
{
    // Recv data through UDP
    GetUDPSocket()->async_receive_from( boost::asio::buffer(m_RecvBufferUDP, MAX_BUFFER_SIZE),
        m_UDPRecvEndpoint,
        boost::bind( &BoostServerConnectionImpl::HandleRecvUDP,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::HandleSendTCP( const error_code& _error,
                                        size_t _bytesTransferred )
{
    if (!_error)
        ConnectionImpl::HandleSendTCP();
    else
        std::cout << "ERROR SEND TCP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////



void BoostServerConnectionImpl::HandleSendUDP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
        ConnectionImpl::HandleSendUDP();
    else
        std::cout << "ERROR SEND UDP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::HandleRecvTCP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
    {
        // Compose inner packet from data buffer
        InnerPacket innerPacket(m_RecvBufferTCP, _bytesTransferred);

        // Get its code
        switch ( innerPacket.UnPackCode() )
        {
        // Data packet received
        case InnerPacket::REGULAR:
            m_RecvPacketTCP = innerPacket;
            ConnectionImpl::HandleRecvTCP();
            break;

        default:
            assert(!"Invalid packet type");
            break;
        }

        // Start receive TCP again
        StartReceiveTCP();
    }
    else
        std::cout << "ERROR RECV TCP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::HandleRecvUDP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
    {
        // Compose inner packet from data buffer
        InnerPacket innerPacket(m_RecvBufferUDP, _bytesTransferred);

        // Get its code
        switch ( innerPacket.UnPackCode() )
        {
        // Regular packet with client data
        case InnerPacket::REGULAR:
            {
                // Handle receive packet on specified connection
                ConnectionsByEndpoints::iterator it = m_ConnectionsByEndpoints.find(m_UDPRecvEndpoint);
                if ( it != m_ConnectionsByEndpoints.end() )
                {
                    BoostServerConnectionImpl* conn = it->second;
                    if (conn)
                    {
                        m_RecvPacketUDP = innerPacket;
                        conn->ConnectionImpl::HandleRecvUDP();
                    }
                }
            }
            break;

        // Accept connection confirmation, bind UDP endpoint with connection
        case InnerPacket::CONNECT:
            {
                std::string TCPEndpoint(innerPacket.Data(), 0, _bytesTransferred - 1);
                ConnectionsByAddress::iterator it = m_ConnectionsByAddress.find(TCPEndpoint);
                if ( it != m_ConnectionsByAddress.end() )
                {
                    BoostServerConnectionImpl* currConnection = (*it).second;
                    m_ConnectionsByEndpoints[m_UDPRecvEndpoint] = currConnection;
                    m_EndpointsByConnections[currConnection] = m_UDPRecvEndpoint;

                    // Send confirmation to the client
                    InnerPacket packet( "Confirm", sizeof("Confirm"), InnerPacket::CONFIRM );
                    packet.PackCode();

                    currConnection->SendTCPInner(packet);
                }
            }
            break;

        default:
            assert(!"Invalid packet type");
            break;
        }

        // Start receive UDP again
        StartReceiveUDP();
    }


    // Some strange bugs...
    else if (_error.value() == 10022 || _error.value() == 10061)
        StartReceiveUDP();
    else
    {
        assert(!"Unknown UDP receive error");
        std::cout << "ERROR RECV UDP: " << _error.message() << std::endl;
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::Accept()
{
    // Accept new TCP connections
    GetTCPAcceptor()->async_accept( m_TCPSocket,
        boost::bind( &BoostServerConnectionImpl::HandleAccept,
        this,
        boost::asio::placeholders::error ) );

    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::Connect( const char* _address )
{
    // Empty implementation here, Connect() is for client only, not for server
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::Close()
{
    m_TCPSocket.close();
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::HandleAccept( const error_code& _error )
{
    if (!_error)
    {
        // Also setup UDP
        StartUDPAccept();

        // Send TCP endpoint to the client, client must confirm this endpoint by UDP
        SendTCPEndpointToClient();

        // Start receiving TCP packets
        StartReceiveTCP();

        ConnectionImpl::HandleAccept();
    }
    else
        std::cout << "ERROR ACCEPT: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::StartUDPAccept()
{
    if (!m_UDPAcceptStarted)
    {
        GetUDPSocket()->open( udp::v4() );
        GetUDPSocket()->bind( udp::endpoint(udp::v4(), m_Port) );
        StartReceiveUDP();
        m_UDPAcceptStarted = true;
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::SendTCPEndpointToClient()
{
    // Convert endpoint to string
    std::string endpointStr = 
        boost::lexical_cast<std::string>( m_TCPSocket.remote_endpoint() );

    // Add record to table
    m_ConnectionsByAddress[endpointStr] = this;

    // Compose inner packet
    InnerPacket innerPacket(endpointStr.c_str(),
                            endpointStr.size(),
                            InnerPacket::CONNECT);
    innerPacket.PackCode();

    // Send it
    SendTCPInner(innerPacket);
}


////////////////////////////////////////////////////////////////////////////////


io_service* BoostServerConnectionImpl::GetIOService()
{
    if (!m_IOService)
        m_IOService = new io_service;

    return m_IOService;
}


////////////////////////////////////////////////////////////////////////////////


tcp::acceptor* BoostServerConnectionImpl::GetTCPAcceptor()
{
    if (!m_Acceptor)
        m_Acceptor = new tcp::acceptor( *GetIOService(), tcp::endpoint(tcp::v4(), m_Port) );

    return m_Acceptor;
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::Run()
{
    if (!m_Started)
        m_Thread = boost::thread(&BoostServerConnectionImpl::ThreadProc);
}


////////////////////////////////////////////////////////////////////////////////


void BoostServerConnectionImpl::ThreadProc()
{
    m_Started = true;
    GetIOService()->run();
    m_Started = false;
}


////////////////////////////////////////////////////////////////////////////////


udp::socket* BoostServerConnectionImpl::GetUDPSocket()
{
    if (!m_UDPSocket)
        m_UDPSocket = new udp::socket( *GetIOService() );

    return m_UDPSocket;
}


////////////////////////////////////////////////////////////////////////////////


udp::endpoint BoostServerConnectionImpl::GetUDPEndpoint( BoostServerConnectionImpl* _this )
{
    EndpointsByConnections::iterator it = m_EndpointsByConnections.find(_this);
    if ( it != m_EndpointsByConnections.end() )
        return it->second;
    else
        return udp::endpoint();
}


////////////////////////////////////////////////////////////////////////////////
